'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

const objectToString = (obj) => Object.prototype.toString.call(obj);

const toStringType = (obj) => objectToString(obj).slice(8, -1);

function isObject(obj) {
  return obj !== null && typeof obj === "object";
}

const isArray = (obj) => Array.isArray(obj);

const isNaN = (obj) => Number.isNaN(obj);

const isInteger = (obj) => Number.isInteger(obj);

const isType = (obj, type) => {
  return toStringType(obj) === type;
};

const isFunction = (obj) => {
  return toStringType(obj) === "Function";
};

function isBoolean(obj) {
  return toStringType(obj) === "Boolean";
}

function isString(obj) {
  return typeof obj === "string";
}

function isNumber(obj) {
  return toStringType(obj) === "Number";
}

function isPlainObject(obj) {
  return toStringType(obj) === "Object";
}

const isMap = (obj) => {
  return toStringType(obj) === "Map";
};

const isSet = (obj) => {
  return toStringType(obj) === "Set";
};

const isNull = (obj) => {
  return toStringType(obj) === "Null";
};

const isUndefined = (obj) => {
  return toStringType(obj) === "Undefined";
};

const isPromise = (obj) => {
  return toStringType(obj) === "Promise";
};

const isSymbol = (obj) => {
  return toStringType(obj) === "Symbol";
};

const isEmpty = (obj) => {
  if (isString(obj) || isArray(obj)) return obj.length === 0;
  if (isSet(obj) || isMap(obj)) return obj.size === 0;
  if (isPlainObject(obj)) return Reflect.ownKeys(obj).length === 0;
  throw new Error("Not one of [String, Array, Set, Map, PlainObject]");
};

const isOdd = (n) => !!(n & 1);
const isEven = (n) => !(n & 1);

/**
 * 下载资源
 * @param {String} url 资源路径
 * @param {String} fileName 文件名
 */
function aDownload(url, fileName) {
  // html5 A[download]
  const anchor = document.createElement("a");
  anchor.href = url;
  anchor.setAttribute("download", fileName);
  anchor.style.display = "none";
  document.body.appendChild(anchor);
  setTimeout(function () {
    anchor.click();
    document.body.removeChild(anchor);
    setTimeout(function () {
      self.URL.revokeObjectURL(anchor.href);
    }, 250);
  }, 0);
}

function strToBlob(content, type) {
  return new Blob([content], { type });
}

/**
 * 下载文本
 * @param {string} str 文本
 * @param {string} fileName 文件名
 * @param {string} type 文件MIME类型
 */
function downloadString(str, fileName, type = "") {
  const src = URL.createObjectURL(strToBlob(str, type));
  aDownload(src, fileName);
}

function downloadJson(content, fileName) {
  const type = "application/json";
  downloadString(
    isString(content) ? content : JSON.stringify(content, null, 2),
    fileName + ".json",
    type
  );
}

function downloadSvg(content, fileName) {
  const type = "image/svg+xml";
  downloadString(content, fileName + ".svg", type);
}

/**
 * 获取网站的MD引用
 * @returns 网站的MD引用
 */
function getReference() {
  return `[${document.title}](${window.location.href})`;
}

/**
 * 复制到剪贴板
 * @param {String} value
 */
function copy2Clipboard(value) {
  const input = document.createElement("input");
  document.body.appendChild(input);
  input.setAttribute("value", value);
  input.select();
  if (document.execCommand("copy")) {
    document.execCommand("copy");
  }
  document.body.removeChild(input);
}

/**
 * canvas 对象
 */
function createCanvas(width = 300, height = 150) {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  return canvas;
}

/**
 * 根据 src 获取图片高度
 * @param {string} src dataURL
 * @returns {Promise<[width, height]>}
 */
function getImageHeight(src) {
  return new Promise((res, rej) => {
    const img = new Image();
    img.src = src;
    img.onload = function () {
      res([this.width, this.height]);
    };
    img.onerror = function (e) {
      rej(e);
    };
  });
}

/**
 * 读取文件内容
 * @param {File} file
 * @param {string} encode
 * @returns string
 */
async function readFileAsText(file, encode) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function () {
      resolve(reader.result);
    };
    reader.onerror = function () {
      reject(reader.result);
    };
    reader.readAsText(file, encode);
  });
}

/**
 * 图片转换为DataURL
 * @param {File} img
 */
async function readImageAsDataURL(img) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function () {
      resolve(reader.result);
    };
    reader.onerror = function () {
      reject(reader.error);
    };
    reader.readAsDataURL(img);
  });
}

/**
 * 图片转换为DataURL(回调格式)
 * @param {File} img
 * @param {Function} cb
 */
function parseImageAsDataURL(img, cb) {
  const reader = new FileReader();
  reader.onload = function () {
    cb(reader.result);
  };
  reader.readAsDataURL(img);
}

/**
 * 防抖
 * @param {Function} func
 * @param {Number} delay
 * @returns {Function}
 */
function debounce(func, delay) {
  let timer = null;
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(() => {
      func(...args);
      timer = null;
    }, delay);
  };
}

/**
 * 节流
 * @param {Function} func
 * @param {Number} delay
 * @returns {Function}
 */
function throttle(func, delay) {
  let timer = null;
  let fresh = false;
  let lastCallArgs = [];
  const funcProxy = function (...args) {
    if (!timer) {
      fresh = false;
      timer = setTimeout(() => {
        func(...args);
        timer = null;
        if (fresh) {
          funcProxy(...lastCallArgs);
        }
      }, delay);
    } else {
      fresh = true;
      lastCallArgs = args;
    }
  };
  return funcProxy;
}

/**
 * 监听 dom size变化时执行 func
 * @param {Function} func 回调
 * @param {Element} dom 元素
 * @returns 取消观察的方法
 */
function whenResize(func, dom, delay = 750) {
  const _func = debounce(func, delay);
  let prev = [0, 0];
  const resizeObserver = new ResizeObserver((entries) => {
    for (const entry of entries) {
      if (
        prev[0] !== entry.target.clientWidth ||
        prev[1] !== entry.target.clientHeight
      ) {
        _func([entry.target.clientWidth, entry.target.clientHeight]);
        prev = [entry.target.clientWidth, entry.target.clientHeight];
      }
    }
  });
  resizeObserver.observe(dom);

  return () => resizeObserver.unobserve(dom);
}

/**
 * 灰度化
 * @param {ImageData} imageData
 * @returns {ImageData}
 */
function grayImageData(imageData) {
  const { data, width, height } = imageData;
  const _data = new Uint8ClampedArray(height * width * 4);
  for (let i = 0; i < data.length; i += 4) {
    const [Red, Green, Blue, Alpha] = data.slice(i, i + 4);
    const gray = Red * 0.3 + Green * 0.59 + Blue * 0.11;
    _data[i] = gray;
    _data[i + 1] = gray;
    _data[i + 2] = gray;
    _data[i + 3] = Alpha;
  }

  return new ImageData(_data, width, height);
}

/**
 * 图片降维: 四位归一灰度化
 * @param {ImageData} imageData
 * @returns - { data, width, height }
 */
function reduceImageData(imageData) {
  const { data, width, height } = imageData;
  const _data = new Uint8ClampedArray(height * width);
  for (let i = 0; i < data.length; i += 4) {
    const [Red, Green, Blue] = data.slice(i, i + 4);
    const gray = Red * 0.3 + Green * 0.59 + Blue * 0.11;
    _data[(i / 4) >>> 0] = gray;
  }

  return { data: _data, width, height };
}

const random = Math.random;
const floor = Math.floor;

/**
 * 随机整数
 * @param {number} n
 * @returns [0, n)
 */
function intRand(n) {
  return floor(random() * n);
}

/**
 * 从范围[a, b)内随机
 * @param {number} a start
 * @param {number} b end
 * @returns [a, b)
 */
function rand(a, b) {
  if (b) {
    const diff = b - a;
    return a + intRand(diff);
  }
  return intRand(a);
}

/**
 * pick
 * @param {Array} arr array
 */
function pick(arr) {
  return arr[rand(arr.length)];
}

/**
 * 随机HEX颜色
 */
function hexColor() {
  return (
    "#" +
    Array(6)
      .fill(0)
      .map(() => rand(16).toString(16))
      .join("")
  );
}

/**
 * 随机RGB颜色
 */
function rgbColor() {
  return `rgb(${Array(3)
    .fill(0)
    .map(() => rand(256))
    .join(",")})`;
}

/**
 * 随机字母
 */
function alphabet() {
  const lower = rand(97, 122);
  const upper = rand(65, 90);
  return String.fromCharCode(pick([lower, upper]));
}

/**
 * 生成随机彩色图片URL
 */
function randImage(
  text,
  width = rand(50, 500),
  height = rand(50, 500),
  bgColor = hexColor(),
  fontColor = "white"
) {
  const canvas = createCanvas(width, height);
  const ctx = canvas.getContext("2d");
  ctx.fillStyle = bgColor;
  ctx.fillRect(0, 0, width, height);

  ctx.fillStyle = fontColor;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.font = Math.min(width, height) / 2 + "px bold";
  ctx.fillText(text, width / 2, height / 2);

  return canvas.toDataURL();
}

/**
 * 分捆
 * @param {Array} array
 * @param {Number} size
 * @returns {Array}
 */
const chunk = (array, size = 1) => {
  let newArray = [];
  for (let i = 0; i < array.length; i = i + size) {
    newArray.push(array.slice(i, i + size));
  }
  return newArray;
};

/**
 * 扁平数组
 * @param {Array} array
 * @param {Number} depth
 * @returns {Array}
 */
const flat = (array, depth = 1) => {
  if (depth > 0) {
    return array.reduce((a, c) => {
      const cur = Array.isArray(c) ? flat(c, depth - 1) : [c];
      return [...a, ...cur];
    }, []);
  }
  return array.slice();
};

/**
 * 生成一个size大小的数组
 * @param {Number} size
 * @param {Function} func
 * @returns {Array}
 */
const map = (size, func = () => {}) => {
  return Array(size)
    .fill(0)
    .map((_, i) => func(i));
};

/**
 * 删除指定值
 * @param {Array} arr 源数组
 * @param  {Array} values 要删除的值
 */
const remove = (arr, ...values) =>
  arr.filter((e) => !values.includes(e));

/**
 * 返回数组合并结果
 * @param  {Array} arrays
 */
const union = (...arrays) => arrays.reduce((a, c) => a.concat(c), []);

/**
 * 交集
 * @param  {...any} array
 */
const intersection = (...arrays) =>
  arrays.reduce((a, c) => a.filter((e) => c.includes(e)));

/**
 * 差异
 */
const difference = (array, values) =>
  array.filter((v) => !values.includes(v));

/**
 * 比较
 */
const compare = (array, values) => [
  array.filter((v) => !values.includes(v)),
  values.filter((v) => !array.includes(v)),
];

const _compare = (array, values) => {
  const uniqueA = new Set(array);
  const uniqueV = new Set(values);

  for (const a of array) {
    for (const v of values) {
      if (a === v) {
        uniqueA.delete(a);
        uniqueV.delete(v);
        continue;
      }
    }
  }

  return [uniqueA.values(), uniqueV.values()].map((v) => Array.from(v));
};

/**
 * 交换
 */
function swap(array, i, j) {
  let temp = array[i];
  array[i] = array[j];
  array[j] = temp;
}

/**
 * 交换式洗牌
 */
const shuffle = (array) => {
  for (let i = array.length - 1; i > 0; i--) {
    swap(array, i, rand(i));
  }
};

/**
 * 蓄水池抽样
 * @param {Array} array 模拟大容量数组，长度为 N
 * @param {number} k 抽取K个 (K < N)
 */
const reservoir = (array, k) => {
  const pool = array.slice(0, k);

  let i = k + 1;
  while (i < array.length) {
    const r = rand(i); // [0, k+1)
    if (r < k) {
      pool[r] = array[i];
    }
    i++;
  }
  return pool;
};

/**
 * 记录最大值
 * @param {Number} peak
 */
const memoPeak = (peak = 0) => {
  return (num = 0) => {
    if (num > peak) {
      peak = num;
    }
    return peak;
  };
};

/**
 * 移除属性
 * @param {Object} obj
 * @param {Array} props
 * @returns Object
 */
const omit = (obj, props) => {
  const _obj = { ...obj };
  props.forEach((prop) => {
    delete _obj[prop];
  });
  return _obj;
};

/**
 * 获得一个函数，执行该函数times(不含)次之后执行func
 * @param {Number} times
 * @param {Function} func
 * @returns {Function}
 */
const after = (times, func) => {
  if (times < 1) return func();
  return (...args) => {
    if (--times < 0) return func(...args);
  };
};

/**
 * 执行 times(含)次之后 不再执行 func
 * @param {Number} times
 * @param {Function} func
 * @returns {Function}
 */
const before = (times, func) => {
  return (...args) => {
    if (times-- > 0) func(...args);
  };
};

/**
 * 只取前n个参数
 * @param {Function} func
 * @param {Number} n
 * @returns {Function}
 */
function ary(func, n = func.length) {
  return (...args) => func(...args.slice(0, n));
}

/**
 * 科里化
 * @param {Function} func
 * @param {Number} len 限制参数长度
 * @returns {Function | any}
 */
function curry(func, len = func.length) {
  return (...args) => {
    if (len > args.length) return curry(func.bind(null, ...args));
    else return func(...args);
  };
}

/**
 * 单例
 * @param {Function} createInstance
 */
function singleton(createInstance) {
  let instance;
  return function (...args) {
    if (!instance) {
      instance = createInstance(...args);
    }
    return instance;
  };
}

/**
 * 带单位的字节数据
 * @param {Number} byteSize 字节
 * @param {Number} fixed 小数保留位
 * @returns {String} 0.00MB
 */
const toByteSize = (byteSize = 0, fixed = 2) => {
  return (
    abbrNum({ value: byteSize, base: 1024, fixed, unitCapital: true }) + "B"
  );
};

/**
 * 简化数字
 */
function abbrNum({
  value = 0,
  fixed = 2,
  base = 1000,
  unitCapital = false,
}) {
  const K = base ** 1;
  const M = base ** 2;
  const G = base ** 3;
  const T = base ** 4;

  let size = value;
  let unit = "T";
  if (size >= T) {
    size = size / T;
    unit = "T";
  } else if (size >= G) {
    size = size / G;
    unit = "G";
  } else if (size >= M) {
    size = size / M;
    unit = "M";
  } else if (size > K) {
    size = size / K;
    unit = "K";
  } else {
    unit = "";
  }
  unit = unitCapital ? unit.toUpperCase() : unit.toLowerCase();
  return (size % 1 === 0 ? size : size.toFixed(fixed)) + unit;
}

/**
 * 返回格式为`00:00:00`的时间
 * @param {Number} milliSeconds 毫秒时间
 * @returns {String} 00:00:00
 */
const time = (milliSeconds = 0) => {
  const dayTime = 24 * 60 * 60 * 1000;
  if (milliSeconds > dayTime) {
    return "-- : -- : --";
  }

  const standard = new Date("2000-1-1 00:00:00");
  const relative = new Date(standard.getTime() + milliSeconds);
  const H = relative.getHours();
  const M = relative.getMinutes();
  const S = relative.getSeconds();

  return [H, M, S].map((t) => ("0" + t).slice(-2)).join(":");
};

/**
 * 保留定长，不足补零
 * @param {String | Number} num
 * @param {Number} len 要保留的长度
 * @returns {String}
 */
const cut = (num, len) => {
  return num.toString().padStart(len, "0").slice(-len);
};

const DATE_TIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
const DATE_FORMAT = "YYYY-MM-DD";
const DATE_FORMAT_TM = "YYYY-MM-DD HH:mm";

/**
 * 格式化日期
 * @param {Date} date
 * @param {String} format
 */
const dateFormat = (date = new Date(), format = DATE_TIME_FORMAT) => {
  const Y = date.getFullYear();
  const M = date.getMonth() + 1;
  const D = date.getDate();
  const H = date.getHours();
  const h = H % 12;
  const m = date.getMinutes();
  const s = date.getSeconds();

  return format
    .replace(/Y{4,}/g, cut(Y, 4))
    .replace(/Y{3}/g, cut(Y, 3))
    .replace(/Y{2}/g, cut(Y, 2))
    .replace(/Y/g, cut(Y, 4))
    .replace(/M{2,}/g, cut(M, 2))
    .replace(/M/g, M)
    .replace(/D{2,}/g, cut(D, 2))
    .replace(/D/g, D)
    .replace(/H{2,}/g, cut(H, 2))
    .replace(/H/g, H)
    .replace(/h{2,}/g, cut(h, 2))
    .replace(/h/g, h)
    .replace(/m{2,}/g, cut(m, 2))
    .replace(/m/g, m)
    .replace(/s{2,}/g, cut(s, 2))
    .replace(/s/g, s);
};

/**
 * 中划线转小驼峰
 * @param {String} str
 */
const camelCase = (str) => {
  return str.replace(/-(.)/g, (_, w) => w.toUpperCase());
};

/**
 * 驼峰转下划线
 * @param {String} str
 */
const snakeCase = (str) => {
  return str
    .replace(/(?<=[^A-Z])[A-Z]/g, (w) => {
      return "_" + w.toLowerCase();
    })
    .toLowerCase();
};

/**
 * 大写首字母
 * @param {String} word
 */
const capitalize = (word) => {
  return word[0].toUpperCase() + word.slice(1);
};

/**
 * 超过长度显示...
 * @param {String} str 文字
 * @param {Number} maxLength 保留最大长度
 * @param {String} padding 填充
 * @returns {String}
 */
const ellipsis = (str = "", maxLength = 20, padding = ".") =>
  str.length > maxLength
    ? str.slice(0, maxLength - 3).padEnd(maxLength, padding)
    : str;

/**
 * 从对象建立URL参数
 * @param {Object} obj 参数对象
 * @returns {URLSearchParams}
 */
function buildParams(obj) {
  const params = new URLSearchParams();
  for (const key in obj) {
    const value = obj[key];
    if (value === undefined) continue;

    if (Array.isArray(value)) {
      value.forEach((v) => params.append(key, v));
      continue;
    }

    params.set(key, value);
  }
  return params;
}

/**
 * 格式化数字
 * @param {Number} num 待处理的数字
 * @param {String} delimiter 分隔符
 * @param {Number} splitLength 几位分割一次
 * @returns {String}
 */
function formatNumber(num, delimiter = "_", splitLength = 3) {
  if (num < 0) {
    return "-" + formatNumber(-num, delimiter, splitLength);
  }

  const level = 10 ** splitLength;

  let quotient = num,
    remainder = 0,
    res = "";
  do {
    remainder = quotient % level;
    quotient = Math.floor(quotient / level);
    res = remainder + ((res ? delimiter : "") + res);
  } while (quotient);

  return res;
}

/**
 * 递归法，数组拼接树，需要预知根id
 * @param {Number} id 根id
 * @param {Array} list 列表
 * @returns 树
 */

/**
 * 列表转换成树
 * @param {Array} list 列表
 * @returns 树
 */
function listToTree(list) {
  const tree = [];

  const nodeMap = new Map();
  for (const node of list) {
    nodeMap.set(node.id, { children: [], ...node });
  }

  for (const node of nodeMap.values()) {
    const parent = nodeMap.get(node.pid);
    (parent ? parent.children : tree).push(node);
  }

  return tree;
}

/**
 * 树转换成列表
 * @param {Array} tree 树
 * @returns 列表
 */
function treeToList(tree) {
  const list = [...tree];
  for (let i = 0; i < list.length; i++) {
    const { children } = list[i];
    if (children) list.splice(i + 1, 0, ...children);
  }
  return list;
}

exports.DATE_FORMAT = DATE_FORMAT;
exports.DATE_FORMAT_TM = DATE_FORMAT_TM;
exports.DATE_TIME_FORMAT = DATE_TIME_FORMAT;
exports._compare = _compare;
exports.aDownload = aDownload;
exports.abbrNum = abbrNum;
exports.after = after;
exports.alphabet = alphabet;
exports.ary = ary;
exports.before = before;
exports.buildParams = buildParams;
exports.camelCase = camelCase;
exports.capitalize = capitalize;
exports.chunk = chunk;
exports.compare = compare;
exports.copy2Clipboard = copy2Clipboard;
exports.createCanvas = createCanvas;
exports.curry = curry;
exports.dateFormat = dateFormat;
exports.debounce = debounce;
exports.difference = difference;
exports.downloadJson = downloadJson;
exports.downloadString = downloadString;
exports.downloadSvg = downloadSvg;
exports.ellipsis = ellipsis;
exports.flat = flat;
exports.formatNumber = formatNumber;
exports.getImageHeight = getImageHeight;
exports.getReference = getReference;
exports.grayImageData = grayImageData;
exports.hexColor = hexColor;
exports.intersection = intersection;
exports.isArray = isArray;
exports.isBoolean = isBoolean;
exports.isEmpty = isEmpty;
exports.isEven = isEven;
exports.isFunction = isFunction;
exports.isInteger = isInteger;
exports.isMap = isMap;
exports.isNaN = isNaN;
exports.isNull = isNull;
exports.isNumber = isNumber;
exports.isObject = isObject;
exports.isOdd = isOdd;
exports.isPlainObject = isPlainObject;
exports.isPromise = isPromise;
exports.isSet = isSet;
exports.isString = isString;
exports.isSymbol = isSymbol;
exports.isType = isType;
exports.isUndefined = isUndefined;
exports.listToTree = listToTree;
exports.map = map;
exports.memoPeak = memoPeak;
exports.objectToString = objectToString;
exports.omit = omit;
exports.parseImageAsDataURL = parseImageAsDataURL;
exports.pick = pick;
exports.rand = rand;
exports.randImage = randImage;
exports.readFileAsText = readFileAsText;
exports.readImageAsDataURL = readImageAsDataURL;
exports.reduceImageData = reduceImageData;
exports.remove = remove;
exports.reservoir = reservoir;
exports.rgbColor = rgbColor;
exports.shuffle = shuffle;
exports.singleton = singleton;
exports.snakeCase = snakeCase;
exports.strToBlob = strToBlob;
exports.throttle = throttle;
exports.time = time;
exports.toByteSize = toByteSize;
exports.toStringType = toStringType;
exports.treeToList = treeToList;
exports.union = union;
exports.whenResize = whenResize;
